// 版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package ssa

// 布局以f命令基本块，目标是最小化控制流指令。
// 此阶段返回后，f.块的顺序很重要，这些块将以
// 的顺序出现在程序集输出中。
func layout(f *Func) {
	f.Blocks = layoutOrder(f)
}

// 寄存器分配可能使用不同的顺序，该顺序具有线性扫描算法施加的约束
// 。
func layoutRegallocOrder(f *Func) []*Block {
	// 实验的残余；也许还会有另一个。
	return layoutOrder(f)
}

func layoutOrder(f *Func) []*Block {
	order := make([]*Block, 0, f.NumBlocks())
	scheduled := make([]bool, f.NumBlocks())
	idToBlock := make([]*Block, f.NumBlocks())
	indegree := make([]int, f.NumBlocks())
	posdegree := f.newSparseSet(f.NumBlocks()) // 剩余度为正的块
	defer f.retSparseSet(posdegree)
	// 剩余度为零的块。使用slice模拟后进先出队列，实现
	// 深度优先拓扑排序算法。
	var zerodegree []ID
	// 后进先出队列。跟踪计划块的后续块，以便当我们遇到循环时，我们选择计划最近一个计划块的后续块。
	var succs []ID
	exit := f.newSparseSet(f.NumBlocks()) // 退出块
	defer f.retSparseSet(exit)

	// 填充idToBlock并查找退出块。
	for _, b := range f.Blocks {
		idToBlock[b.ID] = b
		if b.Kind == BlockExit {
			exit.add(b.ID)
		}
	}

	// 扩大出口，以包括出口区块占主导地位的区块。
	for {
		changed := false
		for _, id := range exit.contents() {
			b := idToBlock[id]
		NextPred:
			for _, pe := range b.Preds {
				p := pe.b
				if exit.contains(p.ID) {
					continue
				}
				for _, s := range p.Succs {
					if !exit.contains(s.b.ID) {
						continue NextPred
					}
				}
				// 所有成功都已退出；add p.
				exit.add(p.ID)
				changed = true
			}
		}
		if !changed {
			break
		}
	}

	// 初始化每个块的indegree 
	for _, b := range f.Blocks {
		if exit.contains(b.ID) {
			// 退出块总是安排在最后一个
			continue
		}
		indegree[b.ID] = len(b.Preds)
		if len(b.Preds) == 0 {
			// 将一个元素推到队列的尾部。
			zerodegree = append(zerodegree, b.ID)
		} else {
			posdegree.add(b.ID)
		}
	}

	bid := f.Entry.ID
blockloop:
	for {
		// 添加块以调度
		b := idToBlock[bid]
		order = append(order, b)
		scheduled[bid] = true
		if len(order) == len(f.Blocks) {
			break
		}

		// 在这里，遍历b.Succs的顺序影响拓扑
		// 排序深入的方向。以下面的cfg为例，不考虑其他因素。
		// b1 
		// 0/\1 
		// b2 b3 
		// 遍历b.成功按顺序，右边的子节点b3将立即调度在
		// b1之后，反之，左边的子节点b2将立即调度在b1之后。测试结果表明，反向遍历的
		// 性能稍好。ABCFDG 
		for i := len(b.Succs) - 1; i >= 0; i-- {
			c := b.Succs[i].b
			indegree[c.ID]--
			if indegree[c.ID] == 0 {
				posdegree.remove(c.ID)
				zerodegree = append(zerodegree, c.ID)
			} else {
				succs = append(succs, c.ID)
			}
		}

		// 选择要调度的下一个块
		// 从尚未调度的后续块中选择。

		// 如果有可能，请使用可能的方向。
		var likely *Block
		switch b.Likely {
		case BranchLikely:
			likely = b.Succs[0].b
		case BranchUnlikely:
			likely = b.Succs[1].b
		}
		if likely != nil && !scheduled[likely.ID] {
			bid = likely.ID
			continue
		}

		// 暂时使用学位。
		bid = 0
		// 待办事项：改进本部分
		// 之前计划的区块工程没有后续工程。
		// 如果可能的话，选择一个零度街区。
		for len(zerodegree) > 0 {
			// 从队列尾部弹出一个元素。
			cid := zerodegree[len(zerodegree)-1]
			zerodegree = zerodegree[:len(zerodegree)-1]
			if !scheduled[cid] {
				bid = cid
				continue blockloop
			}
		}

		// 仍然没有，请选择最近遇到的未计划的后续块。
		for len(succs) > 0 {
			// 从队列尾部弹出一个元素。
			cid := succs[len(succs)-1]
			succs = succs[:len(succs)-1]
			if !scheduled[cid] {
				bid = cid
				continue blockloop
			}
		}

		// 仍然没有，选择任何非出口区块。
		for posdegree.size() > 0 {
			cid := posdegree.pop()
			if !scheduled[cid] {
				bid = cid
				continue blockloop
			}
		}
		// 选择任何出口街区。
		// 待办事项：订购这些以最小化跳跃距离？
		for {
			cid := exit.pop()
			if !scheduled[cid] {
				bid = cid
				continue blockloop
			}
		}
	}
	f.laidout = true
	return order
	// f.Blocks=订单
}
